#ifndef SRC_DRAWER_BASIC_H
#define SRC_DRAWER_BASIC_H

#include <vector>
#include <memory>
#include <chrono>

#include "image.h"
#include "vec.h"

class drawer_basic
{
    std::chrono::duration<long long int, std::ratio<1, 1000000000>> start_timer_point;

protected:
    std::shared_ptr<image> p_img;
    std::shared_ptr<int> a;
    color3 paint;

public:

    void start_timer(){
        start_timer_point =std::chrono::high_resolution_clock::now().time_since_epoch();
    }

    void end_timer()
    {
        auto end = std::chrono::high_resolution_clock::now().time_since_epoch();
        auto micro = std::chrono::duration_cast<std::chrono::microseconds>(end-start_timer_point).count();
        std::cout << "time: " << micro / 1000. << " ms" << std::endl;
    }

    static vec2 get_direction(double radian)
    {
        return vec2(std::cos(radian), std::sin(radian));
    }

    static vec2 to_int(vec2& point)
    {
        return {std::floor(point.x), std::floor(point.y)};
    }
    constexpr const static double PI = M_PI;
    constexpr const static double DELTA = 10e-6;

    drawer_basic() = default;

    void set_image(std::shared_ptr<image> img)
    {
        this->p_img = img;
    }

    void draw_uv(){
//        auto image_width = p_img->width;
//        auto image_height = p_img->height;
//
//        for (int row = 0; row < image_height; ++row) {
//            if(row % 10 == 0){
//                std::cout << "\rScan lines remaining: " << image_height - row << ' ' << std::flush;
//            }
//            for (int col = 0; col < image_width; ++col) {
//                color3 pixel_color(double(row) / image_height, double(col) / image_width, 0.0f);
//                p_img->write_color(row, col, pixel_color);
//            }
//        }
        draw_gradient({0, 0, 0},
                      {1, 0, 0},
                      {0, 1, 0},
                      {1, 1, 0});
        std::cout << "\nDone.\n";
    }

    void draw_gradient(const color3& c00,
                       const color3& c10,
                       const color3& c01,
                       const color3& c11)
    {
        auto image_width = p_img->width;
        auto image_height = p_img->height;

        for (int y = 0; y < image_height; ++y) {
            for (int x = 0; x < image_width; ++x) {
                float v = double(y) / (image_height-1);
                float u = double(x) / (image_width-1);

                auto colx1 = lerp(c00, c10, u);
                auto colx2 = lerp(c01, c11, u);
                auto colf = lerp(colx1, colx2, v);
                p_img->write_color(x, y, colf);
            }
        }
    }

    void paint_color(const color3& pcol){
        auto image_width = p_img->width;
        auto image_height = p_img->height;

        for (int row = 0; row < image_height; ++row) {
            for (int col = 0; col < image_width; ++col) {
                p_img->write_color(col, row, pcol);
            }
        }
    }

    void set_paint(const color3& pcol){
        paint = pcol;
    }

    bool draw_point(const vec2& p){
        return draw_point(p, paint);
    }

    bool draw_point(const vec2& p, double c)
    {
        return draw_point(p, get_rainbow(c));
    }

    bool draw_point(const vec2& p, const color3& color){
        return p_img->write_color(p, color);
    }

    color3 get_rainbow(double v)
    {
        return hsv_to_rgb(color3(v, 1, 1));
    }

    color3 hsv_to_rgb(const color3& c)
    {
        const color3 K = color3(1.0, 2.0 / 3.0, 1.0 / 3.0);
        auto c1 = frac(c.x + K) * 6.0f;
        auto c2 = c1 - color3(3,3,3);
        color3 c3 = fabs( c2);
        auto kx = color3(K.x,K.x, K.x);
        auto c4 = lerp(kx, saturate(c3 - kx), c.y);
        return c.z * c4;
    }

    void draw_line(vec2 start, vec2 end, const color3& col)
    {
//        start = vec2(static_cast<int>(start.x), static_cast<int>(start.y));
//        end = vec2(static_cast<int>(end.x), static_cast<int>(end.y));
        auto vect = vec2(end - start);
        auto length_square = vect.length_squared();
        auto dir = unit_vector(vect);

        int sign_x = dir.x / fabs(dir.x);
        int sign_y = dir.y / fabs(dir.y);

        vec2 step(0, 0);
        int second_step = 1;

        while(step.length_squared() <= length_square){
            if(!draw_point(start + step, col))
                break; // 图像越界
            if(fabs(dir.x) > fabs(dir.y)) {
                step.x += sign_x;
                auto delta = std::fabs(step.x / dir.x * dir.y);
                if(dir.y != 0 && delta > second_step - DELTA){
                    step.y += sign_y;
                    second_step += 1;
                }
            } else{
                step.y += sign_y;
                auto delta = std::fabs(step.y / dir.y * dir.x);
                if(dir.x != 0 && delta > second_step - DELTA){
                    step.x += sign_x;
                    second_step += 1;
                }
            }
        }
    }

    void draw_line(const vec2& p, double length, double radian, const color3& col)
    {
        auto dir = get_direction(radian);
        draw_line(p, p + dir * length, col);
    }

    void draw_line(const vec2& p, double length, double dir, double c)
    {
        draw_line(p, length, dir, get_rainbow(c));
    }

    void draw_line(const vec2& p, double length, double dir)
    {
        draw_line(p, length, dir, paint);
    }

    void draw_box(const vec2& lb, const vec2& size)
    {
        draw_line(lb, size.x, 0);
        draw_line(lb, size.y, PI/2);
        auto rt = lb + size;
        draw_line(rt, size.x, -PI);
        draw_line(rt, size.y, -PI/2);
    }

    // 圆形
    // brekenham画圆法
    void fill_circle(const vec2& p, double radius, const color3& col)
    {
        //正上方第一个点
        auto point = vec2(0, radius);
        fill_circle_point(p, point, col);

        // d0 = (point + (1, 0.5))^2 - radius^2;
        auto d = 1.25 - radius;
        while(point.y >= point.x){
            if(d < 0){
                d = d + 2 * point.x + 3;
                point.x += 1;
            } else {
                d = d + 2 * point.x - 2 * point.y + 5;
                point.x += 1;
                point.y -= 1;
            }
            fill_circle_point(p, point, col);
        }
    }

    void fill_circle(const vec2& p, double radius)
    {
        fill_circle(p, radius, paint);
    }

private:
    void draw_circle_point(const vec2& c, const vec2& d, const color3& col)
    {
        draw_point({c.x + d.x, c.y + d.y}, col);
        draw_point({c.x + d.x, c.y - d.y}, col);
        draw_point({c.x - d.x, c.y + d.y}, col);
        draw_point({c.x - d.x, c.y - d.y}, col);

        draw_point({c.x + d.y, c.y + d.x}, col);
        draw_point({c.x + d.y, c.y - d.x}, col);
        draw_point({c.x - d.y, c.y + d.x}, col);
        draw_point({c.x - d.y, c.y - d.x}, col);
    }

    void fill_circle_point(const vec2& c, const vec2& d, const color3& col)
    {
        draw_line({c.x - d.x, c.y + d.y}, {c.x + d.x, c.y + d.y}, col);
        draw_line({c.x - d.x, c.y - d.y}, {c.x + d.x, c.y - d.y}, col);

        draw_line({c.x - d.y, c.y + d.x}, {c.x + d.y, c.y + d.x}, col);
        draw_line({c.x - d.y, c.y - d.x}, {c.x + d.y, c.y - d.x}, col);
    }
public:
    // 空心圆
    // 中点画圆法
    void draw_circle(const vec2& p, double radius, const color3& col)
    {
        //正上方第一个点
        auto point = vec2(0, radius);
        draw_circle_point(p, point, col);

        // d0 = (point + (1, 0.5))^2 - radius^2;
        auto d = 1.25 - radius;
        while(point.y >= point.x){
            if(d < 0){
                d = d + 2 * point.x + 3;
                point.x += 1;
            } else {
                d = d + 2 * point.x - 2 * point.y + 5;
                point.x += 1;
                point.y -= 1;
            }
            draw_circle_point(p, point, col);
        }
    }

    void draw_circle(const vec2& p, double radius)
    {
        draw_circle(p, radius, paint);
    }

    // 空心多边形
    void draw_polygon(std::vector<vec2> points, const color3& col)
    {
        for (auto& p : points) {
            p.x = static_cast<int>(p.x);
            p.y = static_cast<int>(p.y);
        }

        for (int i = 0; i < points.size(); ++i) {
            auto end = points[(i+1)%points.size()];
            draw_line(points[i], end, col);
        }
    }

    void draw_polygon(const std::vector<vec2>& points)
    {
        draw_polygon(points, paint);
    }

    // 活化边法 多边形扫描转换
    // from https://blog.csdn.net/keneyr/article/details/83747501
    void fill_polygon(std::vector<vec2> polygon, const color3& col)
    {
        auto equal = [](double l, int r){
            return int(l) == r;
        };
//        for (auto& point : polygon) {
//            point.x = round(point.x);
//            point.y = round(point.y);
//        }

        /*定义结构体用于活性边表AET和新边表NET*/
        typedef struct XET
        {
            double x;
            double dx, ymax;
            XET* next;
        }AET,NET;

        //CPoint *polygon;
        int vertNum = polygon.size();

        /*计算最高点y的坐标,扫描线扫到y的最高点就结束*/
        int MaxY = polygon[0].y;
        int MinY = polygon[0].y;
        int i;
        for (i = 1; i < vertNum; i++){
            if (polygon[i].y>MaxY)
                MaxY = polygon[i].y;

            if (MinY > polygon[i].y)
                MinY = polygon[i].y;
        }

        /*初始化AET表，这是一个有头结点的链表*/
        AET *pAET = new AET;
        pAET->next = nullptr;

        /*初始化NET表，这也是一个有头结点的链表，头结点的dx，x，ymax都初始化为0*/
        std::vector<NET*> pNET;
        for (i = 0; i <= MaxY; i++){
            auto temp = new NET();
            pNET.push_back(temp);
            pNET[i]->dx = 0;
            pNET[i]->x = 0;
            pNET[i]->ymax = 0;
            pNET[i]->next = nullptr;
        }

        /*扫描并建立NET表*/
        for (i = MinY; i <= MaxY; i++){
            /*i表示扫描线，扫描线从多边形的最底端开始，向上扫描*/
            for (int j = 0; j < vertNum;j++)
                /*如果多边形的该顶点与扫描线相交，判断该点为顶点的两条直线是否在扫描线上方
                 *如果在上方，就记录在边表中，并且是头插法记录，结点并没有按照x值进行排序，毕竟在更新AET的时候还要重新排一次
                 *所以NET表可以暂时不排序
                */
                if (equal(polygon[j].y, i)){
                    /*笔画前面的那个点*/
                    if (polygon[(j - 1 + vertNum) % vertNum].y > polygon[j].y){
                        NET *p = new NET;
                        p->x = polygon[j].x;
                        p->ymax = polygon[(j - 1 + vertNum) % vertNum].y;
                        p->dx = double((polygon[(j - 1 + vertNum) % vertNum].x - polygon[j].x)) / double((polygon[(j - 1 + vertNum) % vertNum].y - polygon[j].y));
                        p->next = pNET[i]->next;
                        pNET[i]->next = p;
                    }
                    /*笔画后面的那个点*/
                    if (polygon[(j + 1 + vertNum) % vertNum].y > polygon[j].y){
                        NET *p = new NET;
                        p->x = polygon[j].x;
                        p->ymax = polygon[(j + 1 + vertNum) % vertNum].y;
                        p->dx = double((polygon[(j + 1 + vertNum) % vertNum].x - polygon[j].x)) / double((polygon[(j + 1 + vertNum) % vertNum].y - polygon[j].y));
                        p->next = pNET[i]->next;
                        pNET[i]->next = p;
                    }
                }
        }


        /*建立并更新活性边表AET*/
        for (i = MinY; i <= MaxY; i++){
            /*更新活性边表AET，计算扫描线与边的新的交点x，此时y值没有达到临界值的话*/
            NET *p = pAET->next;
            while (p){
                p->x = p->x + p->dx;
                p = p->next;
            }

            /*更新完以后，对活性边表AET按照x值从小到大排序*/
            AET *tq = pAET;
            p = pAET->next;
            tq->next = NULL;
            while (p){
                while (tq->next&&p->x >= tq->next->x)
                    tq = tq->next;
                NET *s = p->next;
                p->next = tq->next;
                tq->next = p;
                p = s;
                tq = pAET;
            }

            /*从AET表中删除ymax==i的结点*/
            AET *q = pAET;
            p = q->next;
            while (p){
                if (equal(p->ymax, i)){
                    q->next = p->next;
                    delete p;
                    p = q->next;
                }
                else{
                    q = q->next;
                    p = q->next;
                }
            }
            /*将NET中的新点加入AET，并用插入法按X值递增排序*/
            p = pNET[i]->next;
            q = pAET;
            while (p){
                while (q->next&&p->x >= q->next->x)
                    q = q->next;
                NET *s = p->next;
                p->next = q->next;
                q->next = p;
                p = s;
                q = pAET;
            }

            /*配对填充颜色*/
            p = pAET->next;
            while (p&&p->next){
                for (int j = static_cast<int>(p->x); j <= p->next->x; j++){
                    draw_point(vec2(j, i),col);
                }
                p = p->next->next;
            }
        }

    }

    void fill_polygon(const std::vector<vec2>& polygon)
    {
        fill_polygon(polygon, paint);
    }
};

#endif